
#include "ToxDeskApp.h"
#include "util/httplib.hpp"
#include "persistence/settings.h"
#include "core/core.h"
#include "core/coreav.h"
#include "util/json.hpp"
#include "model/status.h"
#include "net/bootstrapnodeupdater.h"

#include<string>
#include<algorithm>
#include <thread>

#include <cassert>
#include "audio/audio.h"
#include <vpx/vpx_image.h>

bool is_file_exist(const char *fileName)
{
    std::ifstream infile(fileName);
    return infile.good();
}


static ToxDeskApp* nexus{ nullptr };

ToxDeskApp::ToxDeskApp()
{}

ToxDeskApp::~ToxDeskApp()
{
}


void ToxDeskApp::start()
{
	audioControl = std::unique_ptr<IAudioControl>(Audio::makeAudio(*settings));
	assert(audioControl != nullptr);
	core->getAv()->setAudio(*audioControl);
	core->start();
}


void  ToxDeskApp::loadProfile(const std::string& toxdatapath, const std::string& password)
{
	if (password.empty()) {
		encrypted = false;
	}
	else {
		encrypted = true;
	}
	toxDataPaths = toxdatapath;
	std::vector<unsigned char> toxsave = std::vector<unsigned char>();


    if (is_file_exist(toxdatapath.c_str())) {
        std::unique_ptr<ToxEncrypt> tmpKey = loadToxData(password, toxdatapath, toxsave);
		if (tmpKey == nullptr) {
			return;
		}
		passkey = std::move(tmpKey);
		initCore(toxsave, false);
		simpleId = settings->getSimpleId();
		if (simpleId.size() == 0) {
			regSimpleId();
		}
	}
	else {

		std::cout << "The tox save file " << toxdatapath << " was not found" << std::endl;
		if (!password.empty()) {
			passkey = ToxEncrypt::makeToxEncrypt(password);
			if (!passkey) {
				std::cout << "Failed to derive key of the tox save file" << std::endl;
				return;
			}
		}
		initCore(toxsave, true);
		regSimpleId();
	}
}

void ToxDeskApp::loadbootstrapNodes(const std::string& nodepath)
{
	bootstrapNodes = std::unique_ptr<BootstrapNodeUpdater>(
		new BootstrapNodeUpdater(nodepath));
}


std::string ToxDeskApp::setPassword(const std::string& newPassword)
{
	if (newPassword.empty()) {
		// remove password
		encrypted = false;
	}
	else {
		std::unique_ptr<ToxEncrypt> newpasskey = ToxEncrypt::makeToxEncrypt(newPassword);
		if (!newpasskey) {
			std::cout
				<< "Failed to derive key from password, the profile won't use the new password" << std::endl;
			return (
				"Failed to derive key from password, the profile won't use the new password.");
		}
		// apply change
		passkey = std::move(newpasskey);
		encrypted = true;
	}

	// apply new encryption
	onSaveToxSave();

	bool dbSuccess = false;


	std::string error{};
	if (!dbSuccess) {
		error = ("Couldn't change database password, it may be corrupted or use the old "
			"password.");
	}
	return error;
}

void ToxDeskApp::onSaveToxSave()
{
	std::vector<unsigned char> data = core->getToxSaveData();
	assert(data.size());
	saveToxSave(data);
}

bool ToxDeskApp::saveToxSave(std::vector<unsigned char> data)
{
	std::cout << "Saving tox save to " << toxDataPaths << std::endl;

	std::ofstream outfile(toxDataPaths, std::ios::out | std::ios::binary);

	if (!outfile.is_open()) {
		std::cout << "Tox save file " << toxDataPaths << " couldn't be opened" << std::endl;
		return false;
	}

	if (encrypted) {
		data = passkey->encrypt(data);
		if (data.size() == 0) {
			std::cout << "Failed to encrypt, can't save!" << std::endl;
			return false;
		}
	}
	outfile.write((char*)&data[0], data.size());

	// check if everything got written
	if (outfile.flush()) {
	}
	else {
		std::cout << "Failed to write, can't save!" << std::endl;
		return false;
	}
	return true;
}

void ToxDeskApp::initCore(const std::vector<unsigned char>& toxsave, bool isNewProfile)
{
	if (toxsave.size() == 0 && !isNewProfile) {
		std::cout << "Existing toxsave is empty" << std::endl;
	}

	if (toxsave.size() > 0 && isNewProfile) {
		std::cout << "New profile has toxsave data" << std::endl;
	}

	Core::ToxCoreErrors err;
	core = Core::makeToxCore(toxsave, settings, *bootstrapNodes, &err);
	if (!core) {
		switch (err) {
		case Core::ToxCoreErrors::BAD_PROXY:
			std::cout << "badProxy" << std::endl;
			break;
		case Core::ToxCoreErrors::ERROR_ALLOC:
		case Core::ToxCoreErrors::FAILED_TO_START:
		case Core::ToxCoreErrors::INVALID_SAVE:
		default:
			break;
		}

		std::cout << "Failed to start Toxcore" << std::endl;
		return;
	}

	coreAv = CoreAV::makeCoreAV(core.get(), core->getTox(), *settings);
	if (!coreAv) {
		std::cout << "Failed to start ToxAV" << std::endl;
		return;
	}

	// Tell Core that we run with AV before doing anything else
	core->setAv(coreAv.get());
	coreAv->start();

	if (isNewProfile) {
		core->setUsername("ToxDesk");
		onSaveToxSave();
	}

	// save tox file when Core requests it
	core->saveRequest.connect(&ToxDeskApp::onSaveToxSave, this);
}

void ToxDeskApp::regSimpleId() 
{
	const std::string simpleIdMapAddress{ "81.71.161.184:8001" };
	httplib::Client cli(simpleIdMapAddress);

	std::string url = "/api/SimpleIdMap?toxKey=" + core->getSelfId().toString();
	auto res = cli.Post(url.c_str());
	auto err = res.error();
	std::cout << httplib::to_string(err) << std::endl;
	auto resvalue = res.value();
	if (resvalue.status == 200) {
		std::cout << resvalue.body << std::endl;
		auto jsondoc =   nlohmann::json::parse(res->body);
		if (jsondoc.is_null()) {
			std::cout <<"SimpleIdMap return error data" << std::endl;
		}
		else {
			std::cout << jsondoc["simpleId"] << std::endl;
            simpleId = std::to_string(jsondoc["simpleId"].get<int64_t>());
            settings->setSimpleId(simpleId);
		}
	}
}
std::string ToxDeskApp::getToxKeyFromSimpleId(std::string& id)
{
	auto isSpace = [](const unsigned char c)
	{
		return std::isspace(c);
	};
	id.erase(std::remove_if(id.begin(), id.end(), isSpace), id.end());

	std::string result = "";
	const std::string simpleIdMapAddress{ "81.71.161.184:8001" };
	httplib::Client cli(simpleIdMapAddress);

	std::string url = "/api/SimpleIdMap?simpleId=" + id;
	auto res = cli.Get(url.c_str());
	auto err = res.error();
	std::cout << httplib::to_string(err) << std::endl;
	auto resvalue = res.value();
	if (resvalue.status == 200) {
		std::cout << resvalue.body << std::endl;
		auto jsondoc = nlohmann::json::parse(res->body);
		if (jsondoc.is_null()) {
			std::cout << "SimpleIdMap return error data" << std::endl;
		}
		else {
			std::cout << jsondoc["toxKey"] << std::endl;
			result = jsondoc["toxKey"].get<std::string>();
		}
	}

	return result;
}

std::string& ToxDeskApp::getSimpleId()
{
	return simpleId;
}

std::unique_ptr<ToxEncrypt> ToxDeskApp::loadToxData(const std::string& password, const std::string& filePath, std::vector<unsigned char>& data)
{
	std::unique_ptr<ToxEncrypt> tmpKey;
	int64_t fileSize = 0;
//	std::filesystem::path saveFile(filePath);
//	std::cout << "Loading tox save " << filePath << std::endl;

//	if (!std::filesystem::is_regular_file(saveFile)) {
//		std::cout << "The tox save file " << filePath << " couldn't be opened" << std::endl;
//		return nullptr;
//	}

//	fileSize = std::filesystem::file_size(saveFile);
//	if (fileSize <= 0) {
//		std::cout << "The tox save file" << filePath << " is empty!";
//		return nullptr;
//	}

	std::ifstream ifile(filePath, std::ios::binary);
	data.reserve(fileSize);
	data.assign(std::istreambuf_iterator<char>(ifile),
		std::istreambuf_iterator<char>());


	if (ToxEncrypt::isEncrypted(data)) {
		if (password.empty()) {
			std::cout << "The tox save file is encrypted, but we don't have a password!" << std::endl;
			return nullptr;
		}

		tmpKey = ToxEncrypt::makeToxEncrypt(password, data);
		if (!tmpKey) {
			std::cout << "Failed to derive key of the tox save file" << std::endl;
			return nullptr;
		}

		data = tmpKey->decrypt(data);
		if (data.empty()) {
			std::cout << "Failed to decrypt the tox save file" << std::endl;
			return nullptr;
		}
	}
	return tmpKey;
}



void ToxDeskApp::setSettings(Settings* settings)
{
	this->settings = settings;
}

ToxDeskApp& ToxDeskApp::getInstance()
{
	if (!nexus)
		nexus = new ToxDeskApp;

	return *nexus;
}

void ToxDeskApp::destroyInstance()
{
	delete nexus;
	nexus = nullptr;
}

Core* ToxDeskApp::getCore()
{
	ToxDeskApp& nexus = getInstance();
	return nexus.core.get();
}
